﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using System.Xml;
using PIT.Labs.ProjectRepository.Owl2DotNet.Parsers;
using System.Globalization;



namespace PIT.Labs.ProjectRepository.Owl2DotNet.Rdf
{
    /// <summary>
    /// Parser for RDF/OWL XML files.
    /// </summary>
    public class RdfXmlParser : OntologyParserBase
    {
        internal static readonly XNamespace OWL = "http://www.w3.org/2002/07/owl#";
        internal static readonly XNamespace RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
        internal static readonly XNamespace RDFS = "http://www.w3.org/2000/01/rdf-schema#";

        private OntologyImporter oi;

        /// <summary>
        /// Create new RDF/OWL Xml parser
        /// </summary>
        public RdfXmlParser()
        {
            oi = new FileLoader();
        }

        private static string MakeSafeFilename(string filename)
        {
            char[] bad = Path.GetInvalidFileNameChars();
            char[] oldName = filename.ToCharArray();
            char[] betterName = filename.ToCharArray();

            for (int i = 0; i < oldName.Length; i++ )
            {
                if (!bad.Contains(oldName[i]))
                {
                    betterName[i] = oldName[i];
                }
                else
                {
                    betterName[i] = '_';
                }
            }

            return new string(betterName);
        }

        private static string AppendFragment(String uri)
        {
            if (uri.EndsWith("#", false, CultureInfo.InvariantCulture))
            {
                return uri;
            }
            else
            {
                return uri + "#";
            }
        }

        internal static string GetLabel(XElement node)
        {
            if (node.Element(RDFS + "label") != null)
            {
                return node.Element(RDFS + "label").Value;
            }
            else if (node.Attribute(RDF + "about") != null)
            {
                string elementUri = node.Attribute(RDF + "about").Value;
                int delimPos = Math.Max(elementUri.LastIndexOf('#'), elementUri.LastIndexOf('/')) + 1;
                return elementUri.Substring(delimPos);
            }
            else if (node.Attribute(RDF + "ID") != null)
            {
                return node.Attribute(RDF + "ID").Value;
            }
            else
            {
                return node.Name.LocalName;
            }
        }

        internal static string GetElementUri(XElement node)
        {
            string elementUri = "";
            if (node.Attribute(RDF + "about") != null)
            {
                elementUri = node.Attribute(RDF + "about").Value;
                if (elementUri.StartsWith("#", false, CultureInfo.InvariantCulture))
                {
                    elementUri = GetLocalOntologyUri(node) + elementUri.Substring(1);
                }
                else if (String.IsNullOrEmpty(elementUri))
                {
                    elementUri = GetLocalOntologyUri(node);
                }
            }
            else if (node.Attribute(RDF + "ID") != null)
            {
                elementUri = node.Attribute(RDF + "ID").Value;
                elementUri = GetLocalOntologyUri(node) + elementUri;
            }

            return elementUri;
        }

        private static string GetLocalOntologyUri(XElement node)
        {
            var ontoElement = (from x in node.Document.Root.Elements(OWL + "Ontology")
                               where  x.IsBefore(node)
                               select x).Last();
            return AppendFragment(ontoElement.Attribute(RDF + "about").Value);
        }

        private static void CorrectOntologyAbout(XElement docToImport)
        {
            if (docToImport.Element(OWL + "Ontology") != null && String.IsNullOrEmpty(docToImport.Element(OWL + "Ontology").Attribute(RDF + "about").Value))
            {
                string baseUri = docToImport.Attribute(XNamespace.Xml + "base").Value;
                docToImport.Element(OWL + "Ontology").Attribute(RDF + "about").Value = AppendFragment(baseUri);
            }
        }

        internal static void GetAnnotationProperties(RdfEntityBase e)
        {
            foreach(var x in e.Node.Elements())
            {
                try
                {
                    RdfAnnotationProperty ap = e.RdfOntology.GetAnnotationProperty(new Uri(x.Name.NamespaceName+x.Name.LocalName)) as RdfAnnotationProperty;

                    if (ap != null && x.Value != null)
                    {
                        e.annotations[ap] = new RdfValue(x.Value, ap.Range.First());
                    }
                }
                catch {
                    // XXX: Catch better exception and log
                }
            }
        }

        private static string GetQualifiedName(string name, XElement node)
        {
            if (!name.StartsWith("http://", false, CultureInfo.InvariantCulture))
            {
                string localUri = GetLocalOntologyUri(node);
                if (name[0] == '#')
                {
                    name = name.Substring(1);
                }
                name = AppendFragment(localUri) + name;
            }
            return name;
        }

        internal static void GetSuperClasses(RdfClass e)
        {
            var svf = e.Node.Element(OWL + "someValuesFrom");
            if(svf != null){
                string parentUri = GetQualifiedName(svf.Attribute(RDF + "resource").Value, e.Node);
                e.superClasses.Add(e.RdfOntology.GetClass(parentUri));
                return;
            }

            var superClasses = (from x in e.Node.Elements(RDFS + "subClassOf")
                                where x.Attribute(RDF + "resource") != null
                                select GetQualifiedName(x.Attribute(RDF + "resource").Value, x));

            foreach (var c in superClasses)
            {
                e.superClasses.Add(e.RdfOntology.GetClass(c));
            }

            var restrictionClasses = (from x in e.Node.Elements(RDFS + "subClassOf")
                                      where x.Element(OWL + "Restriction") != null
                                      select new { parentClass = x.Element(OWL + "Restriction").Element(OWL + "someValuesFrom"), 
                                                   property = x.Element(OWL + "Restriction").Element(OWL + "onProperty"),
                                                   node = x.Element(OWL + "Restriction")
                                      });

            foreach (var c in restrictionClasses)
            {
                if(c.parentClass != null && c.property != null){
                    e.superClasses.Add(e.RdfOntology.GetRestrictionClass( 
                        GetQualifiedName(c.property.Attribute(RDF+"resource").Value, c.node),
                        GetQualifiedName(c.parentClass.Attribute(RDF + "resource").Value, c.node),
                        c.node
                        ));
                }
            }
        }

        internal static void GetInstanceClasses(RdfIndividual e)
        {
            var classes = (from x in e.Node.Elements(RDF + "type")
                                where x.Attribute(RDF + "resource") != null
                                select GetQualifiedName(x.Attribute(RDF + "resource").Value, x));

            foreach (var c in classes)
            {
                RdfClass parent = e.RdfOntology.GetClass(c);
                e.instanceOf.Add(parent);
            }

        }

        internal static void GetRange(RdfObjectProperty e)
        {
            var classes = (from x in e.Node.Elements(RDFS + "range")
                           where x.Attribute(RDF + "resource") != null
                           select GetQualifiedName(x.Attribute(RDF + "resource").Value, x));
            if (classes.Count() > 0)
            {
                e.range = e.RdfOntology.GetClass(classes.First());
            }
        }

        internal static void GetEquivalentClasses(RdfClass e)
        {
            var equivalentClasses = from x in e.Node.Elements(OWL + "equivalentClass")
                                   where x.Element(OWL + "Class") != null && x.Element(OWL + "Class").Attribute(RDF + "about") != null
                                   select x.Element(OWL + "Class");

            foreach (var c in equivalentClasses)
            {
                string cUri = null;
                if (c.Attribute(RDF + "about") != null)
                {
                    cUri = c.Attribute(RDF + "about").Value;
                }

                if (cUri != null)
                {
                    e.equivalentClasses.Add(e.RdfOntology.GetClass(GetQualifiedName(cUri, c)));
                }
            }
        }

        /// <summary>
        /// Parse the ontology from the given filename
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public override IOntology Load(string filename)
        {
            oi.Dir = Path.GetDirectoryName(filename) + @"\";
            return base.Load(filename);
        }

        /// <summary>
        /// Parse the ontolohy from the given stream
        /// </summary>
        /// <param name="instream"></param>
        /// <returns></returns>
        public override IOntology Load(System.IO.Stream instream)
        {
            RdfOntology o = null;

            XmlReader xmlFromStream = XmlReader.Create(instream, new XmlReaderSettings() { ProhibitDtd = false });
            var doc = XDocument.Load(xmlFromStream);

            var root = doc.Root;

            CorrectOntologyAbout(root);

            oi.ImportOntologies(doc);

            //XXX Need to check if this Element Exists!!
            o = new RdfOntology(root.Element(OWL + "Ontology"));


            var owlElements = from x in root.Descendants()
                              where x.Name.Namespace == OWL && (x.Attribute(RDF + "about") != null || x.Attribute(RDF + "ID") != null)
                              select x;
            
            foreach (var x in owlElements)
            {
                string typename = "owl2DotNet.Ontology.Rdf." + "Rdf" + x.Name.LocalName;
                Type type = Type.GetType(typename);
                if (type != null)
                {
                    Activator.CreateInstance(type, x, o);
                }
            }

            var descriptionElements = from x in root.Descendants()
                                      where x.Name == RDF + "Description"
                                      select x;

            foreach (var x in descriptionElements)
            {
                XElement rdfType = x.Element(RDF + "type");
                if (rdfType != null)
                {
                    string elementUri = rdfType.Attribute(RDF + "resource").Value;
                    int delimPos = Math.Max(elementUri.LastIndexOf('#'), elementUri.LastIndexOf('/'))+1;
                    elementUri = String.Format(CultureInfo.InvariantCulture, "{{{0}}}{1}", elementUri.Substring(0, delimPos), elementUri.Substring(delimPos));
                    XName elementName = elementUri;
                    
                    string typename = "owl2DotNet.Ontology.Rdf." + "Rdf" + elementName.LocalName;
                    Type type = Type.GetType(typename);
                    if (type != null)
                    {
                        Activator.CreateInstance(type, x, o);
                    }
                }
            }
            
            return o;
        }


        abstract class OntologyImporter
        {
            internal string Dir
            {
                get;
                set;
            }

            public abstract void ImportOntologies(XDocument doc);
        }

        class FileLoader : OntologyImporter
        {
            public override void ImportOntologies(XDocument doc)
            {
                var files = new Dictionary<string, XDocument>();

                foreach (var x in doc.Root.Descendants())
                {
                    string currentRessource = x.Name.Namespace.NamespaceName;

                    if (x.Name == OWL + "imports")
                    {
                        currentRessource = x.Attribute(RDF + "resource").Value;
                    }

                    if (!files.ContainsKey(currentRessource))
                    {
                        files.Add(currentRessource, null);

                        string filename = Dir + currentRessource.Substring(currentRessource.LastIndexOf('/')+1).Replace("#","");

                        if (File.Exists(filename))
                        {
                            XElement docToImport = XDocument.Load(filename).Root;

                            CorrectOntologyAbout(docToImport);
                            doc.Root.Add(docToImport.Elements());
                        }
                        else
                        {
                            // XXX: Log the file not found error
                            //Console.WriteLine("FNF");
                        }
                    }
                }
            }
        }

        class WebDownloader : OntologyImporter
        {
            public override void ImportOntologies(XDocument doc)
            {
                var files = new Dictionary<string, XDocument>();

                //files.Add(OWL.NamespaceName, null);
                //files.Add(RDF.NamespaceName, null);
                //files.Add(RDFS.NamespaceName, null);

                XAttribute xmlBase = doc.Root.Attribute(XNamespace.Xml + "base");

                if (xmlBase != null)
                {
                    files.Add(AppendFragment(xmlBase.Value), null);
                }

                using (var loader = new WebClient())
                {
                    loader.Headers.Add("Accept: application/rdf+xml, application/xml");

                    var root = doc.Root;
                    foreach (var x in root.Descendants())
                    {
                        string currentRessource = x.Name.Namespace.NamespaceName;

                        //if (x.Name == OWL + "imports")
                        //{
                        //    currentRessource = x.Attribute(RDF + "resource").Value;
                        //}

                        if (!files.ContainsKey(currentRessource))
                        {
                            files.Add(currentRessource, null);

                            string filename = Dir + MakeSafeFilename(currentRessource);

                            if (!Directory.Exists(Dir))
                            {
                                Directory.CreateDirectory(Dir);
                            }

                            if (!File.Exists(filename) || File.GetLastWriteTime(filename) < DateTime.Now.Subtract(new TimeSpan(24, 0, 0)))
                            {
                                loader.DownloadFile(new Uri(currentRessource), filename);
                            }

                            XElement docToImport = XDocument.Load(filename).Root;

                            if (docToImport.Element(OWL + "Ontology") != null)
                            {
                                CorrectOntologyAbout(docToImport);
                                doc.Root.Add(docToImport.Elements());
                            }
                        }
                    }
                }
            }
        }        
    }
}
